import logging
import time

import dateutil.parser as dt_parser

import coin.exchange.base.kr_rest.level_book as level_book_mdl
import coin.exchange.base.kr_rest.public_client_base as pubcb
import coin.exchange.gdax.kr_rest.native_public_client as npubc

from coin.exchange.base.kr_rest.rest_client_base import RestUpdate
from coin.exchange.gdax.kr_rest.product import GdaxProduct
from coin.exchange.gdax.kr_rest.constants import active_symbol_list
from coin.proto.coin_market_query_pb2 import (
    ProductTicker,
    ExchangeTicker,
    ProductKlineElement,
    ProductKline,
)


class GdaxFeedParser(object):
  @staticmethod
  def parse_native_ticker(update_msg: dict, product):
    exchange_timestamp = int(dt_parser.parse(update_msg['time']).timestamp() * 10**9)
    return ProductTicker(last=float(update_msg['price']),
                         ask=float(update_msg['ask']),
                         bid=float(update_msg['bid']),
                         exchange_timestamp=exchange_timestamp,
                         volume_24hr=float(update_msg['volume']),
                         native_symbol=product.native_symbol,
                         symbol=product.symbol)

  @staticmethod
  def parse_level_book(update_msg, product):
    level_book = level_book_mdl.LevelBook(product)
    for elem in update_msg['asks']:
      level_elem = level_book_mdl.LevelElement(float(elem[0]), float(elem[1]), elem[2])
      level_book.add_ask(level_elem)
    for elem in update_msg['bids']:
      level_elem = level_book_mdl.LevelElement(float(elem[0]), float(elem[1]), elem[2])
      level_book.add_bid(level_elem)
    level_book.finalize()
    return level_book

  @staticmethod
  def parse_native_kline(update_msg, product, kline_period, start_time, end_time):
    kline_group = []
    for kline in update_msg:
      kline_timestamp = kline[0]
      if kline_timestamp > start_time.timestamp() and \
        kline_timestamp < end_time.timestamp():
        product_kline = ProductKlineElement(
            kline_timestamp=int(kline[0] * 1e+9),
            low=float(kline[1]),
            high=float(kline[2]),
            open=float(kline[3]),
            close=float(kline[4]),
            volume=float(kline[5]),
        )
        kline_group.append(product_kline)
    assert len(kline_group) > 0
    return ProductKline(
        symbol=product.symbol,
        native_symbol=product.native_symbol,
        exchange='Gdax',
        market_type='Spot',
        kline_period=str(kline_period),
        klines=kline_group,
    )


class GdaxPublicClient(pubcb.PublicClientBase):
  def __init__(self):
    self.npubc = npubc.GdaxNativePublicClient()

  # Official document recommends listening on *match* message in the ws stream.
  def query_ticker_impl(self, product):
    symbol = product.native_symbol
    update = self.npubc.get_product_ticker(symbol)
    update.msg = GdaxFeedParser.parse_native_ticker(update.msg, product)
    return update

  def query_exchange_ticker_impl(self):
    raw_tickers = []
    product_ticker_list = []
    req_time = time.time()
    for symbol in active_symbol_list:
      update = None
      try:
        update = self.npubc.get_product_ticker(symbol)
        raw_tickers.append(update.msg)
        product = GdaxProduct.FromStr(symbol)
        product_ticker_list.append(GdaxFeedParser.parse_native_ticker(update.msg, product))
      except Exception as e:
        if update is not None:
          logging.info(f'status: {update.status}, native_msg: {update.native_msg}')
        logging.info('Fail to query ticker: Gdax-%s. %s'% (symbol, e))
        continue
    res_time = time.time()

    exchange_ticker = ExchangeTicker(exchange='Gdax', each_ticker=product_ticker_list)
    update = RestUpdate(req_time, res_time, raw_tickers)
    update.msg = exchange_ticker
    return update

  def query_history_kline_impl(self, product, kline_period, start_time, end_time):
    symbol = product.native_symbol
    update = self.npubc.get_history_kline(symbol, kline_period, start_time, end_time)
    update.msg = GdaxFeedParser.parse_native_kline(update.msg,
                                                   product,
                                                   kline_period,
                                                   start_time,
                                                   end_time)
    return update

  def query_level_book_impl(self, product):
    symbol = product.native_symbol
    update = self.npubc.get_products_book(symbol)
    update.msg = GdaxFeedParser.parse_level_book(update.msg, product)
    return update
